import {
  addIcon, App,
  Editor,
  EditorPosition,
  EditorSuggest,
  EditorSuggestContext,
  EditorSuggestTriggerInfo,
  MarkdownView, Modal, Notice,
  Plugin, PluginSettingTab, Setting, TFile, TFolder, Vault
} from "obsidian";
import {RegexDef} from "./common";
import {SettingTab} from "./settings";
import {ExecuteUI} from "./ui";

interface PluginSettings {
  replacePageRegexes: Array<RegexDef>,
  // replacePageRegex: string,
  // replacePageRegex2: string,
  replacePageTemplate: string,
  replacePageDefaultReplacements: string,
  vaultReplaceStartLocation: string,
  // replaceSelectionRegex: string,
  // replaceSelectionTemplate: string,
  // replaceSelectionDefaultReplacements: string,
}

const DEFAULT_SETTINGS: PluginSettings = {
  // replacePageRegexes: [""],
  replacePageRegexes: [new RegexDef(), new RegexDef()],
  // replacePageRegex: '"(.*)__([0-9a-f]{5,40})(\.\S+)?"',
  // replacePageRegex2: "(\S*)__([0-9a-f]{5,40})(\.\S+)?",
  replacePageTemplate: "",
  replacePageDefaultReplacements: "",
  vaultReplaceStartLocation: "",
  // replaceSelectionRegex: "(\S*)__([0-9a-f]{5,40})(\.\S+)?",
  // replaceSelectionTemplate: "[本地知识库](http://localhost:3000/{{%2}}{{%3}}#~:~hint={{%1}})",
  // replaceSelectionDefaultReplacements: "na, na, na, .html",
}

//Add chevron-up-square icon from lucide for mobile toolbar (temporary until Obsidian updates to Lucide v0.130.0)
addIcon("chevron-up-square", `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-square"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><polyline points="8,14 12,10 16,14"></polyline></svg>`);

export default class SelectionReplacePlugin extends Plugin {
  settings: PluginSettings

  async onload() {
    await this.loadSettings();
    const executeUI: Modal = new ExecuteUI(this.app, this);
    addIcon("replace-all", `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-replace-all-icon lucide-replace-all"><path d="M14 14a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2"/><path d="M14 4a2 2 0 0 1 2-2"/><path d="M16 10a2 2 0 0 1-2-2"/><path d="M20 14a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2"/><path d="M20 2a2 2 0 0 1 2 2"/><path d="M22 8a2 2 0 0 1-2 2"/><path d="m3 7 3 3 3-3"/><path d="M6 10V5a 3 3 0 0 1 3-3h1"/><rect x="2" y="14" width="8" height="8" rx="2"/></svg>`);
    const ribbonIconEl = this.addRibbonIcon('replace-all', 'Selection Replace', (evt: MouseEvent) => {
      executeUI.open();
    });
    this.addCommand({
      id: "replace-selection-text",
      name: "Replace Selection Text",
      icon: "plus-square",
      checkCallback: (checking: boolean) => {
        if (checking)
          return !!this.app.workspace.getActiveViewOfType(MarkdownView);
        this.replaceSelectionText();
      },
    });

    this.addCommand({
      id: "replace-page-text",
      name: "Replace Page Text",
      icon: "plus-square",
      checkCallback: (checking: boolean) => {
        if (checking)
          return !!this.app.workspace.getActiveViewOfType(MarkdownView);
        this.replacePageText();
      },
    });

    this.addCommand({
      id: "replace-vault-text",
      name: "Replace Vault Text",
      icon: "plus-square",
      checkCallback: (checking: boolean) => {
        if (checking)
          return !!this.app.workspace.getActiveViewOfType(MarkdownView);
        this.replaceVaultText();
      },
    });

    this.addCommand({
      id: "clear-redundant-empty-lines (by-page)",
      name: "Clear Redundant Empty Lines (by page)",
      icon: "plus-square",
      checkCallback: (checking: boolean) => {
        if (checking)
          return !!this.app.workspace.getActiveViewOfType(MarkdownView);
        this.clearPageTextRedudantEmptyLines();
      },
    });

    this.addCommand({
      id: "clear-redundant-empty-lines (by-selection)",
      name: "Clear Redundant Empty Lines (by selection)",
      icon: "plus-square",
      checkCallback: (checking: boolean) => {
        if (checking)
          return !!this.app.workspace.getActiveViewOfType(MarkdownView);
        this.clearSelectionTextRedundantEmptyLines();
      },
    });

    // add settings tab
    this.addSettingTab(new SettingTab(this.app, this));
  }

  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  }

  async saveSettings() {
    await this.saveData(this.settings);
  }

  clearSelectionTextRedundantEmptyLines() {
    const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

    if (!mdView) return false;
    if (mdView.editor == undefined) return false;

    const doc = mdView.editor;
    const text = doc.getSelection();
    const replacedText = this.clearRedundantEmptyLines(text);
    doc.replaceSelection(replacedText, text);
  }
  clearPageTextRedudantEmptyLines() {
    const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

    if (!mdView) return false;
    if (mdView.editor == undefined) return false;

    // const regex: RegExp = /(\S*)__([0-9a-f]{5,40})(\.\S+)?/g;
    const text = mdView.data;
    const replacedText = this.clearRedundantEmptyLines(text);
    mdView.editor.setValue(replacedText);
  }
  clearRedundantEmptyLines(text: string) {
    let replacedText = "";
    for (let line of text.trim().split("\n")) {
      if (line.trim() != "") {
        replacedText += line + "\n";
      }
    }
    return replacedText;
  }
  replaceSelectionText(){
    const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

    if (!mdView) return false;
    if (mdView.editor == undefined) return false;

    const doc = mdView.editor;
    const text = doc.getSelection();
    // const regex: RegExp = new RegExp(this.settings.replacePageRegex, 'g');
    // const regex2: RegExp = new RegExp(this.settings.replacePageRegex2, 'g');
    // const template = this.settings.replacePageTemplate.trim();
    // const defaultParamList = this.settings.replacePageDefaultReplacements.split(",").map(item => item.trim());
    // let replacedText = this.replaceText(text, regex, template, defaultParamList);
    // replacedText = this.replaceText(replacedText, regex2, template, defaultParamList);
    const replacedText = this.replaceTextInChain(text);
    doc.replaceSelection(replacedText, text);
  }
  replacePageText() {
    const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

    if (!mdView) return false;
    if (mdView.editor == undefined) return false;

    // const regex: RegExp = /(\S*)__([0-9a-f]{5,40})(\.\S+)?/g;
    const text = mdView.data;
    // const regex: RegExp = new RegExp(this.settings.replacePageRegex, 'g');
    // const regex2: RegExp = new RegExp(this.settings.replacePageRegex2, 'g');
    // const template = this.settings.replacePageTemplate.trim();
    // const defaultParamList = this.settings.replacePageDefaultReplacements.split(",").map(item => item.trim());
    // let replacedText = this.replaceText(text, regex, template, defaultParamList);
    // replacedText = this.replaceText(replacedText, regex2, template, defaultParamList);
    const replacedText = this.replaceTextInChain(text);
    mdView.editor.setValue(replacedText);
  }
  replaceVaultText() {
    const files: TFile[] = [];
    // const startLocation = "2_Cards/个人";
    // const startLocation = "0_Workshop/working/obsidian-selection-replace";
    const startLocation = this.settings.vaultReplaceStartLocation.trim();
    const folderOrFile = this.app.vault.getAbstractFileByPath(startLocation);
    if (folderOrFile instanceof TFile) {
      files.push(folderOrFile);
    } else if (folderOrFile instanceof TFolder) {
      Vault.recurseChildren(folderOrFile, (file => {
        if (file instanceof TFile && file.extension === "md") {
          files.push(file);
        }
      }));
    }

    let openFile = true;
    if (files.length > 3) {
      openFile = false;
    }
    new Notice(`searched ${files.length} files`);
    files.forEach(async markdownFile => {
      const content = await this.app.vault.read(markdownFile);
      const replacedText = this.replaceTextInChain(content);
      this.writeAndOpenFile(
          this.app,
          markdownFile.path,
          replacedText,
          openFile
      );
      // const cacheItem = this.app.metadataCache.getCache(markdownFile.path);
      // console.log(cacheItem)
    });
    // console.log(files);
  }

  replaceTextInChain(content: string): string {
    const regexList: Array<RegExp> = [];
    this.settings.replacePageRegexes.forEach((regexDef, index) => {
      const regex: RegExp = regexDef.regexText.trim() === "" ? null : new RegExp(regexDef.regexText, 'g') ;
      if (regexDef.enabled && regex) {
        regexList.push(regex);
      }
    });
    // const regex: RegExp = this.settings.replacePageRegex.trim() === "" ? null : new RegExp(this.settings.replacePageRegex, 'g') ;
    // const regex2: RegExp = this.settings.replacePageRegex2.trim() === "" ? null : new RegExp(this.settings.replacePageRegex2, 'g');
    const template = this.settings.replacePageTemplate.trim();
    const defaultParamList = this.settings.replacePageDefaultReplacements.split(",").map(item => item.trim());
    const defaultParamMap = new Map;
    for (const defaultParam of defaultParamList){
      const [key, value] = defaultParam.split("=").map(item => item.trim());
      defaultParamMap.set(key, value);
    }
    let replacedText = content;
    for (const regex of regexList) {
      replacedText = this.replaceText(replacedText, regex, template, defaultParamMap);
    }
    // if (regex) {
    //   replacedText = this.replaceText(content, regex, template, defaultParamList);
    // } else if (regex2) {
    //   replacedText = this.replaceText(replacedText, regex2, template, defaultParamList);
    // }
    return replacedText;
  }
  /*
  替换给定text范围内符合正则表达式的内容，替换为tempalte内容
   */
  replaceText(text: string, regex: RegExp, template: string, defaultParamMap: Map<string, string>): string {
    let match;
    let replacedText = text;
    while ((match = regex.exec(text)) != null) {
      const full = match[0];
      const listToReplace = match.map(item => {
        if (item) {
          return item.toString();
        } else {
          return "";
        }
      });
      replacedText = replacedText.replace(full, this.replaceTemplate(template, listToReplace, defaultParamMap));
    }
    return replacedText;
  }

  replaceTemplate(template: string, paramList: Array<string>, defaultParamMap: Map<string, string>): string{
    // const defaultParamList = ["na", "na", "na", ".html"];
    let text = template;
    let paramEscapeList = paramList.map((item, index) => {
      if (item && item != "") {
        return item.replaceAll("(", "%28").replaceAll(")", "%29")
            .replaceAll("[", "%5B").replaceAll("]", "%5D").replaceAll(" ", "%20");
      } else {
        const key = `{{%${index}}}`;
        if (defaultParamMap.has(key)){
          return defaultParamMap.get(key);
        } else {
          return "na";
        }
      }
    });
    if (paramEscapeList.length <= 1 && template.indexOf("{{%1}}") !== -1) { // JM：如果template包含占位符，那么必须paramEscapeList中要有大于1个值，因为第0个是full match
      new Notice(`there is no capture group in regex, please add parentheses around if want capture full text.`);
      throw new Error(`there is no capture group in regex, please add parentheses around if want capture full text.`);
    }
    for (let i=1;i<=paramEscapeList.length;i++) {
      text = text.replace("{{%"+i.toString()+"}}", paramEscapeList[i]); // JM: 这里跳过paramList[0]因为[0]是full match
    }
    return text;
  }

  async writeAndOpenFile(
      app: App,
      outputFileName: string,
      text: string,
      openFile: boolean
  ) {
    await app.vault.adapter.write(outputFileName, text);
    if (!openFile) return;

    let fileIsAlreadyOpened = false;
    app.workspace.iterateAllLeaves((leaf) => {
      if (
          leaf.getDisplayText() != "" &&
          outputFileName.startsWith(leaf.getDisplayText())
      ) {
        fileIsAlreadyOpened = true;
      }
    });
    if (!fileIsAlreadyOpened) {
      const newPane = app.workspace.getLeavesOfType("empty").length == 0;
      if (newPane) {
        app.workspace.openLinkText(outputFileName, "/", true);
      } else {
        const file = app.vault.getAbstractFileByPath(outputFileName);

        if (file instanceof TFile) {
          await app.workspace
              .getLeavesOfType("empty")[0]
              .openFile(file);
        } else {
          app.workspace.openLinkText(outputFileName, "/", true);
        }
      }
    }
  }
}